/******************************************************************************
 *      Mestrado em Ciência da Computação - UFMS
 *		Redes de Computadores - Prof. Dr. Ronaldo Alves Ferreira
 *		Alunos: Geraldo Landre, Marcel Tolentino, Vinícius Bueno
 *****************************************************************************/
 
#include "parser.h"

int get_path(vstring *line,header_data *hd)
{
    int path_size = 0;
    vstring *value = NULL;
    
    while(get_token(&line,&value,SLASH))
    {
        trim(&line);       
        
        if(value != NULL)
        {
            replace_all(&value,"%20"," ");

            if(strcmp(value->pt,"..") != 0)
                hd->stack++;
            else
                hd->stack--;
            
            concat_string(&(hd->path),SLASH);
            concat_vstring(&(hd->path),value);
            copy_vstring(&(hd->file),value);
        }        
    }
    if(hd->path == NULL)
        concat_string(&(hd->path),SLASH);
    
    if(hd->file != NULL)
    {
        remove_substring(&(hd->path),hd->path->length - hd->file->length,hd->file->length);
        path_size += hd->file->length;
    }
    
    path_size += hd->path->length;
    
    if(path_size > URI_MAX_LEN) // verificando tamanho do path
    {
        hd->error = ERR_REQUEST_URI_TOO_LONG;
        return 0;
    }        
    
    if(hd->stack < 0) // verificando se o cliente está tendando acessar antes do root
    {
        hd->error = ERR_FORBIDDEN;
        return 0;
    }
    
    return 1;
}

int parse_first_line(vstring **line,header_data *hd)
{
    vstring *value = NULL;
    int resp_code = 0;

    while(*line != NULL)
    {
        trim(line);
        get_token(line,&value,SPACE);
        if(get_method(&value) > 0) // só retorna maior que 0 se for um método
        {
            hd->method = get_method(&value);
            copy_vstring(&(hd->method_str),value);
        }
        else
            if(strstr(value->pt,"HTTP/1.0") != NULL || strstr(value->pt,"HTTP/1.1") != NULL)
                copy_vstring(&(hd->http),value);
            else
                if(string_to_integer(value,&resp_code)) // se foi transformado em número, é o código de um cabeçalho de resposta
                {
                    hd->error = resp_code;
                    free_string(line);
                }
                else
                    get_path(value,hd);
    }
    return 1;
}

int parse_parameter(vstring **line,header_data **hd)
{
    vstring *copy_line = NULL;
    copy_vstring(&copy_line,*line);
    switch(get_parameter(line))
    {
        case V_END: // 0, indicando que acabou o cabeçalho
            (*hd)->end = 1;
            break;        
        case V_CONNECTION: // 10
            copy_vstring(&((*hd)->header[V_CONNECTION]),copy_line);
            (*hd)->alive = get_connection(line);
            break;
        case V_CONTENT_LENGTH: // 13
            copy_vstring(&((*hd)->header[V_CONTENT_LENGTH]),copy_line);
            (*hd)->content_length = get_number(line);
            //printf("\npegou o content_length: %i\n",(*hd)->content_length);
            break;
        case V_HOST: // 23
            copy_vstring(&((*hd)->header[V_HOST]),copy_line);
            get_host(line,hd);
            break;
        case V_UNKNOW:
            break;
    }
    free_string(&copy_line);
    return 0;
}
int is_first_line(vstring *line)
{
    return (line != NULL && strstr(line->pt,"HTTP/1.") != NULL);
}

int parse(vstring *msg,header_data **hd)
{
    vstring *line = NULL;

    while(!(*hd)->end && get_line(&line,&msg))
    {
        if(is_first_line(line)) // primeira linha da primeira parte da mensagem
            parse_first_line(&line,*hd);
        else
            parse_parameter(&line,hd);
        free_string(&line);
    }
    return (!(*hd)->error || (*hd)->error == MSG_OK);
}

void concat_path(header_data **hd,vstring **path,int complete)
{
    copy_vstring(path,configure.server_dir);
    concat_vstring(path,(*hd)->path);
    
    if((*path)->pt[(*path)->length - 1] != '/')
        concat_string(path,SLASH);

    if(complete && (*hd)->file != NULL)
        concat_vstring(path,(*hd)->file);
}

void concat_request(vstring **request,header_data **hd)
{
    copy_vstring(request,(*hd)->path);

    if((*request)->pt[(*request)->length - 1] != '/')
        concat_string(request,SLASH);

    if((*hd)->file != NULL)
        concat_vstring(request,(*hd)->file);
}

void get_link(header_data **hd,vstring **link)
{    
    copy_vstring(link,(*hd)->path);

    if((*hd)->file != NULL)
        concat_vstring(link,(*hd)->file);
    if((*link)->pt[(*link)->length - 1] != '/')
        concat_string(link,SLASH);
}

int handle_cgibin(vstring* url,vstring **resp,header_data **hd)
{
    vstring *custom_message = NULL;
    
    cgibin_exec(url,custom_message,hd);

    if((*hd)->error != MSG_OK)
    {
        if(custom_message->length > 0)
            *resp = get_html_error((*hd)->error,custom_message->pt);
        else
            *resp = get_html_error((*hd)->error,NULL);
    }
    else
    	*resp = custom_message;
    
    return (*resp)->length;
}

void miss_cache(vstring **body,header_data **hd)
{
    int body_size = 0,found = 0;
    vstring *path_aux = NULL;
    level1 *new_file = NULL;
    DIR *access = NULL;
    FILE *file = NULL;
    struct dirent *accesst = NULL;
    time_t now;

    printf("entrou no miss\n");
    
    concat_path(hd,&path_aux,0);  // criando o path desde o root até o ultimo diretório

    if((*hd)->file != NULL)
    {
        access = opendir(path_aux->pt);
        if(access != NULL)
        {
            while((accesst = readdir(access)) != NULL && !found)
            {
                if(strcmp(accesst->d_name,(*hd)->file->pt) == 0)
                {
                    concat_path(hd,&path_aux,1);  // agora que deu certo, gera o path completo para acessar o que foi requisitado

                    found = 1;
                    switch(accesst->d_type)
                    {
                        case DT_DIR:
                            (*hd)->error = MSG_OK;
                            //printf("antes do if\n\n");
                            if(configure.file_default != NULL)
                            {
                                concat_string(&path_aux,SLASH);
                                concat_vstring(&path_aux,configure.file_default);
                                if((file = fopen(path_aux->pt,"r+")) != NULL)
                                {
                                    fclose(file);
                                    body_size = get_file_size(path_aux);
                                    read_file(path_aux,body);
                                }
                                else
                                    body_size = get_directory_html(body,hd);
                            }
                            else
                                body_size = get_directory_html(body,hd);
                            break;
                        default:
                            (*hd)->error = MSG_OK;

                            body_size = get_file_size(path_aux);
                            read_file(path_aux,body);
                            break;
                    }
                }
            }
            if(!found)
            {
                (*hd)->error = ERR_NOT_FOUND;
                *body = get_html_error((*hd)->error,NULL);
                body_size = (*body)->length;
            }
            closedir(access);
        }
        else
        {
            (*hd)->error = ERR_NOT_FOUND;
            *body = get_html_error((*hd)->error,NULL);
            body_size = (*body)->length;
        }
    }
    else  // caso em que a solicitação é para acesso à pasta root
    {
        (*hd)->error = MSG_OK;
        body_size = get_directory_html(body,hd);
    }
    //print_header_data(*hd);
    
    
    /* de acordo com a RFC 2616, se deu para responder então guarda na cache
     * (item 13.4, pg 57)
     */
    if((*hd)->error == MSG_OK)
    {
        create_level1(&new_file);
        create_meta_data(&(new_file->data));
        create_name_file(&(new_file->data->file_name));
        now = time(NULL);
        new_file->data->last_access = localtime(&now);
        new_file->data->came_from_server = localtime(&now);
        new_file->data->size = body_size;
        copy_vstring(&(new_file->file_data),*body);
        concat_request(&(new_file->data->request),hd);
        printf("tá antes de promover o lock para escrita\n");
        pthread_rwlock_unlock(&cache_semaphore);
        pthread_rwlock_wrlock(&cache_semaphore);
        printf("promoveu!\n");
        insert_in_cache(&new_file);
        pthread_rwlock_unlock(&cache_semaphore);
        printf("liberou na escrita\n");
    }
    copy_vstring(&((*hd)->msg),*body);
}

int handle_request(vstring **resp,header_data **hd)
{
    int resp_len = 0,body_size,found = 0;
    vstring *body = NULL,*header = NULL,*request = NULL;    

    printf("entrou no handle_request\n\n");
            
    
    pthread_rwlock_rdlock(&cache_semaphore);
    //while(!found)
    //{
        concat_request(&request,hd);
        printf("tentando procurar: %s\n",request->pt);
        if((found = search_request_level1(request)) != 0) // se deu hit na cache
        {
            printf("hit level1\n");
            body_size = hit_level1(found,&body);
            pthread_rwlock_unlock(&cache_semaphore);
            (*hd)->error = MSG_OK;
        }
        else
        {
            if((found = search_request_level2(request)) != 0) // se deu hit na cache
            {
                printf("hit level2\n");
                body_size = hit_level2(found,&body);
                pthread_rwlock_unlock(&cache_semaphore);
                (*hd)->error = MSG_OK;
            }
            else
            {
                printf("miss cache\n");
                miss_cache(&body,hd);
            }
        }
        //if((*hd)->error)            
            //found = 1;
   // }
    //pthread_rwlock_unlock(&cache_semaphore);
    
    copy_vstring(&((*hd)->msg),body);
    header = get_header(hd);  
    resp_len = header->length + (*hd)->msg->length;
    copy_vstring(resp,header);
    concat_vstring(resp,(*hd)->msg);

    free_string(&body);
    free_string(&header);
    
    printf("situação da cache antes de enviar a resposta:\n\n");
    print_cache();
    printf("\n\n\n");
    
    return resp_len;
}

vstring* get_header(header_data **hd)
{
    vstring *header = NULL,*len = NULL;

    if((*hd)->http != NULL)
        copy_vstring(&header,(*hd)->http);
    else
        copy_string(&header,"HTTP/1.1");
    concat_string(&header,SPACE);
    concat_string(&header,get_str_httperror((*hd)->error));
    concat_string(&header,END_HEADER_LINE);

    concat_string(&header,"Server: FACOM-MCC-RC-2011/1.0\r\n");
    concat_string(&header,"Content-type: ");
    if((*hd)->file != NULL && get_extension((*hd)->file->pt) != NULL)
        concat_string(&header,get_extension((*hd)->file->pt));
    else
        concat_string(&header,"text/html");
    concat_string(&header,END_HEADER_LINE);
    concat_string(&header,"Content-Length: ");

    len = new_vstring();
    if(!is_null(&((*hd)->msg)))
        integer_to_string(&len,(*hd)->msg->length);
    else
        copy_string(&len,"0");
    concat_vstring(&header,len);
    free_string(&len);
    concat_string(&header,END_HEADER_LINE);

    if((*hd)->alive)
        concat_string(&header,"Connection: keep-alive\r\n\r\n");
    else
        concat_string(&header,"Connection: close\r\n\r\n");
    return header;
}

vstring* get_html_error(int error,char* custom_message)
{
    vstring *html = NULL;

    copy_string(&html,"<head><h1>");
    concat_string(&html,get_str_httperror(error));
    concat_string(&html,"</h1></head><body><table>");
    concat_string(&html,"<tr><td>");

    if(custom_message != NULL)
        concat_string(&html,custom_message);
    else
        concat_string(&html,get_str_httperror(error));

    concat_string(&html,"</tr></td></table></body>\n");

    return html;
}

/******************************************************************************
 * 		get_directory_html - transformação do conteúdo de um diretório em html
 * parametros:
 *		access: diretório aberto.
 * 		path: caminho de diretórios até o diretório atual.
 *
 * Percorre o arquivo gerando o html para os elementos que forem diretórios ou
 * arquivos.
 *
 * retorno: string contendo o corpo da resposta com o html.
 ******************************************************************************/
int get_directory_html(vstring **body,header_data **hd)
{
    vstring *link = NULL,*path = NULL;
    struct dirent *accesst;
    DIR *access;

    get_link(hd,&link); // gerando o link (endereço relativo)
    concat_path(hd,&path,1); // gerando o caminho completo para abrir o diretório
    access = opendir(path->pt);
    if(access != NULL)
    {
        concat_string(body,"<head><h1>");
        concat_vstring(body,link);        
        concat_string(body,"</h1></head><body><table>");

        if((*hd)->file != NULL)
        {
            concat_string(body,"<tr><td><a href=\"");
            concat_vstring(body,link);
            concat_string(body,"../\">..</a></tr></td>");
        }

        while((accesst = readdir(access)) != NULL)
        {
            if((accesst->d_type == DT_REG || accesst->d_type == DT_DIR) && 
               (strcmp(accesst->d_name,".") && strcmp(accesst->d_name,"..")))
            {
                
                if(accesst->d_type == DT_REG)
                    concat_string(body,"<tr><td><img height= \"20\" src=\"/imagens/arquivo.png\"></td>");
                else
                    concat_string(body,"<tr><td><img height= \"20\" src=\"/imagens/diretorio.png\"></td>");
                concat_string(body,"<td><a href=\"");
                concat_vstring(body,link);
                concat_string(body,accesst->d_name);
                concat_string(body,"\">");
                concat_string(body,accesst->d_name);
                concat_string(body,"</a></td></tr>");
            }
        }
        free_string(&link);
        concat_string(body,"</table></body>\n");
    }
    else
    {
        (*hd)->error = ERR_FORBIDDEN;
        *body = get_html_error((*hd)->error, NULL);
    }
    return (*body)->length;
}

vstring* get_maintenance_msg(header_data **hd, char* custom_message)
{
    (*hd)->error = ERR_SERVICE_UNAVAILABLE;
    return get_msg_error(hd, custom_message);
}

vstring* get_msg_error(header_data **hd, char* custom_message)
{
    vstring *header = NULL;

    (*hd)->msg = get_html_error((*hd)->error,custom_message);

    header = get_header(hd);
    concat_vstring(&header,(*hd)->msg);

    return header;
}